package cn.tedu.charging.order.service;

import cn.tedu.charging.common.pojo.message.ChargingStartCheckMessage;
import cn.tedu.charging.common.pojo.message.DelayCheckMessage;
import cn.tedu.charging.common.pojo.param.OrderAddParam;
import cn.tedu.charging.common.pojo.vo.VehicleVO;
import cn.tedu.charging.common.protocol.JsonResult;
import cn.tedu.charging.common.utils.CronUtil;
import cn.tedu.charging.order.emqx.MqttProducer;
import cn.tedu.charging.order.feign.DeviceClient;
import cn.tedu.charging.order.feign.UserClient;
import cn.tedu.charging.order.utils.XxlJobTaskUtil;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.UUID;
import java.util.concurrent.CompletableFuture;

@Service
@Slf4j
public class OrderService {
    @Autowired
    private DeviceClient deviceClient;
    @Autowired
    private UserClient userClient;
    //如果不希望业务层解除实际实现中间件,可以创建ProducerRepository专门做生产发送
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private MqttProducer mqttProducer;
    public String createOrder(OrderAddParam orderAddParam) {
        //1. 每一张订单都要生成一个订单号 应该至少满足2个特点 唯一 有序(排序上升,顺序性) 雪花
        //以始终指针为基础,多个含有业务意义的 有序字段拼接 而成 组成部分(不同雪花算法 不一样)
        //a. 时间戳(13位) b.数据中心ID(服务运行在的机房) c 机器ID d.本机序列号...
        //以始终为基础,有可能导致一个情况出现,时钟回拨,机器时钟值上一秒还是1789097**** 下一秒1789096****
        //001789097**** 001789098**** 001789099**** 01178998
        //82319d-19hd29hrffg2319f-sdhf93
        String orderNo = UUID.randomUUID().toString().replaceAll("-", "");

        //2.检查设备 2.1 设备是否存在 2.2 枪是否是空闲
        CompletableFuture<Void> checkDeviceFuture=CompletableFuture.supplyAsync(()->{
            checkGun(orderAddParam.getGunId());
            return null;
        });
        //3.检查用户 2.2 用户是否存在 2.3 用户是否绑定车辆 2.4 需要车辆id信息
        checkVehicleAndGet(orderAddParam.getUserId());
        CompletableFuture<Void> checkUserFutrue=CompletableFuture.supplyAsync(()->{
            checkVehicleAndGet(orderAddParam.getUserId());
            return null;
        });
        //主线程阻塞等待
        CompletableFuture<Void> allFuture = CompletableFuture.allOf(checkDeviceFuture, checkUserFutrue);
        try {
            allFuture.get();
            checkUserFutrue.get();
            checkDeviceFuture.get();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("设备或用户不存在");
        }
        //4.订单发送命令 告知设备枪桩 开始自检充电 物联网通信协议mqtt emqx
        String startCheckTopic="/charging/device/check/"+orderAddParam.getPileId();
        //String messageJson="{\"orderNo\":\""+orderNo+"\"}";
        ChargingStartCheckMessage chargingStartCheckMessage = new ChargingStartCheckMessage();
        chargingStartCheckMessage.setOrderNo(orderNo);
        chargingStartCheckMessage.setUserId(orderAddParam.getUserId());
        chargingStartCheckMessage.setGunId(orderAddParam.getGunId());
        String messageJson= JSON.toJSONString(chargingStartCheckMessage);
        mqttProducer.sendMessage(startCheckTopic,messageJson);
        //充电桩接到命令 充电桩开始设备自检 然后自检成功 才开始充电,并且告知订单可以创建success
        //自检失败 告诉订单 创fail 但是没有逻辑可以保证充电桩的反馈信息一定可以被订单获取
        //5. 给足充电桩自检的时间,订单要做兜底检查 到底充电桩反馈信息是什么结果 考虑无响应 延迟40秒检查 rabbitmq延迟消息
        DelayCheckMessage delayCheckMessage = new DelayCheckMessage();
        delayCheckMessage.setOrderNo(orderNo);
        delayCheckMessage.setUserId(orderAddParam.getUserId());
        delayCheckMessage.setPileId(orderAddParam.getPileId());
        delayCheckMessage.setGunId(orderAddParam.getGunId());
        delayCheckMessage.setVehicleId(17);
        //交换机可以是fanout 交换机是topic 让业务队列绑定路由 #
        rabbitTemplate.convertAndSend("DELAY_BILL_EX","",delayCheckMessage);
        log.debug("延迟消息已经发送,检查订单是否有结果:{}",delayCheckMessage);
        //6. 订单最终要创建一个 预估最大充电时长的定时任务 调用xxl-job接口
        //6.1 什么时间执行 需要计算 cron表达式 结合当前扫码传递的最大充电时长预估值
        Integer maxChargeTime=1000*60*10;//跟延迟自检 区分开
        String cronExpression = CronUtil.delayCron(maxChargeTime);
        //6.2 调用xxl-job接口 localhost:8080/xxl-job-admin/jobinfo/add?
        //存在2个缺点 第一 每次扫码充电 都要在代码了new RestTemplate() 扫码下单并发一旦高了,很有可能内存溢出
        //第二 当前执行的id 是强耦合数据,并不能动态获取,应该在创建任务之前 检查一下admin调度中心的接口 order-executor的id
        //最终的过程都可以 整合到nacos+openFegin调用 将调度中心看成是一个服务
        XxlJobTaskUtil.createJobTask(new RestTemplate(),cronExpression,5,orderNo,orderAddParam.getUserId());
        //组织一个HTTP请求
        return orderNo;
    }

    private Integer checkVehicleAndGet(Integer userId) {
        //调用UserClient  解析响应
        JsonResult<VehicleVO> vehicleVOJsonResult = userClient.bindedVehicle(userId);
        //如果没有绑定车辆 抛异常 下单方法结束
        VehicleVO data = vehicleVOJsonResult.getData();
        if (data==null||data.getId()==null){
            //说明没有绑定车辆
            throw new RuntimeException("没有绑定车辆");
        }
        return data.getId();
    }

    private void checkGun(Integer gunId) {
        //使用gunId 调用deviceClient 发起远程请求 获取响应结果 解析
        JsonResult<Boolean> booleanJsonResult = deviceClient.checkGun(gunId);
        //解析
        Boolean data = booleanJsonResult.getData();
        if (!data){
            //枪不可用
            throw new RuntimeException("枪状态不可用");
        }
    }
}
